Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Conversation

JunTaoLuo
Copy link
Contributor

@JunTaoLuo JunTaoLuo commented Apr 9, 2018

This is needed for aspnet/KestrelHttpServer#2390:

The usage is:

  1. Extend base class of LoggedTest
    public class MyTests : LoggedTest
    {
    }
  1. Use the properties of the base class LoggedTest
        [Fact]
        public void MyTest()
        {
            // using LoggerFactory
            using (var webhost = WebHost.CreateDefaultBuilder().ConfigureServices(AddTestLogging).Build())
            {
                //...
            }
            // using Logger
            Logger.LogInformation("Hello world");
            // using TestOutputHelper
           new XunitLogger(TestOutpuHelper, nameof(MyClass), LogLevel.Information);
            // using TestSink
            TestSink.Writes.ForEach(c => Assert.True(c.LogLevel >= LogLeve.Debug));
        }
  1. The verbosity can be controlled via the LogLevelAttribute:
        [Fact]
        [LogLevel(LogLevel.Information)]
        public void MyTest()
        {
            // This will noop
            Logger.Debug("Hello world");
        }

@JunTaoLuo JunTaoLuo requested review from analogrelay and pakrym April 9, 2018 17:29
using Xunit.Abstractions;
using Xunit.Sdk;

namespace Microsoft.AspNetCore.Testing.xunit
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to make this type public from testing instead of duplicating here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
Assert.NotNull(LoggerFactory);
LoggerFactory.CreateLogger(nameof(AssemblyTestLogTests)).LogInformation("Hello world");
LoggerFactory.CreateLogger(nameof(AssemblyTestLogTests)).LogDebug("Debug Hello world");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: add more tests

<PropertyGroup>
<Description>Helpers for writing tests that use Microsoft.Extensions.Logging. Contains null implementations of the abstractions that do nothing, as well as test implementations that are observable.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed to cross-target to enable test discovery on full framework.

'\\', // back slash
'/', // forward slash
'\x7F', // delete
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToArray

{
if (InvalidFileChars.Contains(c))
{
var escapedChar = Convert.ToByte(c).ToString("x");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This expands string size a lot, maybe just do '_' ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did do that at one point but it made test names highly unreadable. Also converting to the same character for all invalid chars creates lots of collisions. With the test name shortening logic in place, I don't think this should be an issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm right now I expand illegal characters like / => -x2f, maybe I can reduce the verbosity and just leave it as 2f which is a bit harder to parse out but still retains most of the information.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did do that at one point but it made test names highly unreadable

Hex is not very readable too.

With the test name shortening logic in place, I don't think this should be an issue.

Replacing a bunch of '_' with one would be a nice addition to shortening logic.

Copy link
Contributor Author

@JunTaoLuo JunTaoLuo Apr 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Poor choice of words, I meant file name collisions instead of readability. Even though we have collision resolution, I would prefer retaining as much information as possible of the original arguments used by default. I've shortened the escaping to just the hex.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really prefer Testing_https:-x2f-x2flocalhost over Testing_https:_localhost ? To me, the second one is much easier to figure out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For that case maybe, but this will create many collisions for kestrel tests, for example: https://github.com/aspnet/KestrelHttpServer/blob/dev/test/shared/HttpParsingData.cs#L192. Also I don't like coalescing multiple _ since that makes tests like https://github.com/aspnet/KestrelHttpServer/blob/dev/test/shared/HttpParsingData.cs#L254-L255 indistiguishable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@halter73 what would you prefer?

{
public class LoggedTestCaseRunner : XunitTestCaseRunner
{
public LoggedTestCaseRunner(IXunitTestCase testCase, string displayName, string skipReason, object[] constructorArguments, object[] testMethodArguments, IMessageBus messageBus, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) : base(testCase, displayName, skipReason, constructorArguments, testMethodArguments, messageBus, aggregator, cancellationTokenSource)
Copy link
Contributor

@pakrym pakrym Apr 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linebreaks? This line spans 2 monitors

{
var testClass = base.CreateTestClass();

if (testClass is LoggedTest loggedTestClass)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we do it for any class, so you're not forced to inherit and can just inject the constructor argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not injecting it as a constructor argument. We are setting the property of the base class LoggedTest.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless you are suggesting we change the design?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always thought we are going argument injection, that's why we started with argument injection DI prototype.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a way of removing the need to flow the ITestOutputHelper with constructors.

{
public class LoggedTestXunitTests : LoggedTest
{
public LoggedTestXunitTests(ITestOutputHelper output) : base(output)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still have to pass ITestOutputHelper around? Can ILoggerFactory be injected directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to pass it around since we resolve the ITestOutputHelper from the list of constructor args. Though I think removing it would be a breaking change so we'll need to update all the places currently using LoggedTest.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I don't think we can remove ITestOutputHelper. What if the user wants to initialize a different LoggerFactory by calling StartLog (to override test name for example)? That call needs the ITestOutputHelper.

@JunTaoLuo
Copy link
Contributor Author

Note: WIP adding and fixing tests.

{
public class LoggedTestFrameworkDiscoverer : XunitTestFrameworkDiscoverer
{
private List<(Type Type, IXunitTestCaseDiscoverer Discoverer)> Discoverers { get; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dictionary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, at one point, I was using a best match on the type which needed a list of discoverers for types from the most specific to the most general. Now that I'm using an exact match on the type and falling back to non-logged discoverers, this can totally be just a dictionary.

if (testClass is LoggedTest loggedTestClass)
{
var classType = loggedTestClass.GetType();
var testOutputHelper = ConstructorArguments.Single(a => typeof(ITestOutputHelper).IsAssignableFrom(a.GetType())) as ITestOutputHelper;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory we could get the ITestOutputHelper from the source (whatever that is) rather than extracting it from constructor arguments.

var classType = loggedTestClass.GetType();
var testOutputHelper = ConstructorArguments.Single(a => typeof(ITestOutputHelper).IsAssignableFrom(a.GetType())) as ITestOutputHelper;
var logLevelAttribute = TestMethod.GetCustomAttribute(typeof(LogLevelAttribute)) as LogLevelAttribute;
var testName = TestMethodArguments.Aggregate(TestMethod.Name, (a, b) => $"{a}_{(b ?? "null")}");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string.Join('_', ...)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closest I can come up with is string.Join("_", TestMethod.Name, TestMethodArguments) but that won't be able to mark null object with "null" string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From adding tests previously, I remember it being useful to distinguish between null strings and empty strings for example. That's why I special cased null here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for foldl

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is foldl?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hipster name for Aggregate.

@pakrym
Copy link
Contributor

pakrym commented Apr 9, 2018

Supporting https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.Testing/ITestSink.cs would be interesting too, would make some kestrel tests even simpler

@analogrelay
Copy link
Contributor

analogrelay commented Apr 9, 2018

Ooh, yeah, automatically register a TestLogger and TestSink. I like that. If it's simple, do it, if it's more than a couple minutes work, file a bug and let's get this in.

.Union(new char[] {
' ', // space
'\\', // back slash
'/', // forward slash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this already an InvalidFileNameChar on every platform? Maybe a whitelist would be better.

Copy link
Contributor Author

@JunTaoLuo JunTaoLuo Apr 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so but no harm in duplicates. I prefer not using a whitelist since that probably takes more work to construct.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's way less that could go wrong with a whitelist and a lot less to think about. How do you justify replacing the delete character but not the other control characters?

I think it would take much effort to only allow alphanumeric characters plus underscore and replace the reset with underscore.

var skipReason = testMethod.EvaluateSkipConditions();
return skipReason != null
? new[] { new SkippedTestCase(skipReason, DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod) }
: base.CreateTestCasesForTheory(discoveryOptions, testMethod, theoryAttribute);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the base class not call EvaluateSkipConditions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not LoggedTheoryDiscoverer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But isn't that what you're replacing? If this didn't need to call EvaluateSkipConditions before, why now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I'm replacing ConditionalTheoryDiscoverer. But this inherits from LoggedTheoryDiscoverer which doesn't call EvaluateSkipConditions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't ConditionalTheoryDiscoverer have it's own LoggedTheoryDiscoverer that does exactly this then? Why not use that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I misunderstood. Could you derive from ConditionalTheoryDiscoverer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I'll still need to override these two methods to create LoggedTestCase or LoggedTheoryTestCase. I don't see the benefit here.

var classType = loggedTestClass.GetType();
var testOutputHelper = ConstructorArguments.Single(a => typeof(ITestOutputHelper).IsAssignableFrom(a.GetType())) as ITestOutputHelper;
var logLevelAttribute = TestMethod.GetCustomAttribute(typeof(LogLevelAttribute)) as LogLevelAttribute;
var testName = TestMethodArguments.Aggregate(TestMethod.Name, (a, b) => $"{a}_{(b ?? "null")}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for foldl

private static readonly int MaxPathLength = 245;
private static char[] InvalidFileChars = new char[]
{
'\"', '<', '>', '|', '\0',
Copy link
Contributor Author

@JunTaoLuo JunTaoLuo Apr 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JunTaoLuo
Copy link
Contributor Author

🆙📅

@pakrym
Copy link
Contributor

pakrym commented Apr 10, 2018

Can we bring [assembly: Xunit.TestFramework("Microsoft.Extensions.Logging.Testing.LoggedTestFramework", "Microsoft.Extensions.Logging.Testing")] via MSBuild target?

@pakrym
Copy link
Contributor

pakrym commented Apr 10, 2018

We can also add a method to LoggetTest to change

 [Fact]
        public void MyTest()
        {
            // using LoggerFactory
            using (var webhost = WebHost.CreateDefaultBuilder().ConfigureServices(collection => collection.AddSingleton(LoggerFactory)).Build())
            {
                //...
            }

to

[Fact]
        public void MyTest()
        {
            // using LoggerFactory
            using (var webhost = WebHost.CreateDefaultBuilder().ConfigureServices(AddTestLogging).Build())
            {
                //...
            }

Makes it a bit shorter and simpler.


foreach (var c in s)
{
sb.Append(InvalidFileChars.Contains(c) ? Convert.ToByte(c).ToString("x") : $"{c}");
Copy link
Contributor

@pakrym pakrym Apr 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now when we have space in the invalid character list, please, let's not use hex, a bunch of random lower case characters would make file names completely unreadable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I like keeping the invalid character list to a minimum, but I vote for replacing all the invalid characters with underscore.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only append underscore if the previous character is not underscore. A_B is better than A_____B and might help long paths.

// None resolved so create a new one and retain a reference to it for initialization/uninitialization
if (loggedTestClass.TestOutputHelper == null)
{
loggedTestClass.TestOutputHelper = _output = new TestOutputHelper();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pakrym
Copy link
Contributor

pakrym commented Apr 10, 2018

Couple of comments then LGTM, let's try to use it.

@JunTaoLuo JunTaoLuo force-pushed the johluo/logging-framework-discoverer branch from e86704c to 3e6b132 Compare April 10, 2018 19:32
@JunTaoLuo
Copy link
Contributor Author

Addressed the feedback items.

@JunTaoLuo JunTaoLuo force-pushed the johluo/logging-framework-discoverer branch 2 times, most recently from c9f2f08 to 1b8e402 Compare April 10, 2018 21:45
- Add filename handling for assembly test log
@JunTaoLuo JunTaoLuo force-pushed the johluo/logging-framework-discoverer branch from 1b8e402 to 81b11e8 Compare April 10, 2018 21:58
@JunTaoLuo JunTaoLuo merged commit 81b11e8 into dev Apr 10, 2018
@JunTaoLuo JunTaoLuo deleted the johluo/logging-framework-discoverer branch April 10, 2018 22:00
@analogrelay
Copy link
Contributor

🎉

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants